﻿#region

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using MediaSage.Lib.Configuration;
using MediaSage.Lib.Database;
using MediaSage.Lib.Utilities;

#endregion

namespace MediaSage.Lib.Plugin.FileNameProvider
{
    using System.Collections;
    using System.Globalization;
    using System.IO.Compression;
    using System.Net;
    using System.Text;
    using global::FileNameProvider;
    using TvdbLib;
    using TvdbLib.Cache;
    using TvdbLib.Data;
    using WatTmdb.V3;

    public class FileNameProvider : PluginBase
    {
        #region Fields

        private IList<Regex> regexs;
        private bool useMovieNameDB;
        private string whitespaceRegex;
        private TvdbHandler tvdbHandler = null;
        private Tmdb TMDB_API;
        private Titles titlesClass;
        private int fuzzyMatchDistance;
        private string removalStrings;

        #endregion Fields

        #region Constructors

        /// <summary>
        ///     Base, will try to get the instance from the database based on name and version
        /// </summary>
        /// <param name="Configuration"> </param>
        public FileNameProvider(Config Configuration, PluginEntity PE)
            : base(Configuration, PE)
        {
            logger.Log("Initializing FileNameProvider.FileNameProvider");
            regexs = new List<Regex>();
            useMovieNameDB = false;

            foreach (PluginConfigurationItemEntity piCI in base.pluginEntity.ConfigurationItems)
            {
                switch (piCI.VariableName.ToUpper())
                {
                    case "WHITESPACE":
                        whitespaceRegex = "[ .-]";
                        break;

                    case "REGEX":
                        logger.Log("Adding regex: " + piCI.Value);
                        var regex = new Regex(piCI.Value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
                        regexs.Add(regex);
                        break;

                    case "USEMOVIENAMEDB":
                        if (piCI.Value.Equals("1") || piCI.Value.Equals("TRUE"))
                        {
                            useMovieNameDB = true;
                        }
                        break;

                    case "REMOVALSTRINGS":
                        logger.Log("Setting removal strings to: " + piCI.Value);
                        removalStrings = piCI.Value;
                        break;

                    case "FUZZYMATCHDISTANCE":
                        if (!int.TryParse(piCI.Value, out fuzzyMatchDistance))
                        {
                            logger.Log(String.Format("Error parsing FuzzyMatchDistance, value was: {0}", piCI.Value));
                            fuzzyMatchDistance = 2;
                        }
                        logger.Log(String.Format("Setting FuzzyMatchDistance={0}", fuzzyMatchDistance));
                        break;

                    default:
                        logger.Log(Logger.Levels.WARN,
                                   "Unknown variable: " + piCI.VariableName + " Value: " + piCI.Value + " Type: " +
                                   piCI.ValueType.ToString());
                        break;
                }
            }

            logger.Log("FileNameProvider.FileNameProvider initialized");
        }

        #endregion Constructors

        public override bool Run(MediaClass mediaObject)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Not applicable for this plugin
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override IList<PersonEntity> SearchPersonByName(string name)
        {
            throw new NotImplementedException();
        }

        public override string[] About()
        {
            VersionInfo verInfo = GetVersionInfo();
            ;
            string[] returnValue =
                {
                    "FileNameProvider v" + verInfo.MajorVersion + "." + verInfo.MinorVersion + "." + verInfo.BuildNumber
                    ,
                    "Makes educated guesses on the file name to get data about the program in question.  This",
                    "requires TVDBProvider and TMDBProvider."
                };
            return returnValue;
        }

        public override IList<MediaFileTypesEntity> ExtensionsHandled()
        {
            return base.pluginEntity.ExtensionsHandled;
        }

        public override PluginEntity GetConfiguration()
        {
            return base.pluginEntity;
        }

        public override MediaClass GetMedia(MediaClass inputMedia, MediaMergeType mergeType)
        {
            throw new NotImplementedException();
        }

        public override MediaClass GetMediaByID(object ID)
        {
            throw new NotImplementedException();
        }

        public override MediaClass GetUpdateForMedia(MediaClass inputMedia, MediaMergeType MergeType)
        {
            throw new NotImplementedException();
        }

        public override VersionInfo GetVersionInfo()
        {
            VersionInfo verInfo;
            verInfo.Name = Assembly.GetExecutingAssembly().GetName().Name;
            verInfo.MajorVersion = Assembly.GetExecutingAssembly().GetName().Version.Major;
            verInfo.MinorVersion = Assembly.GetExecutingAssembly().GetName().Version.Minor;
            verInfo.BuildNumber = Assembly.GetExecutingAssembly().GetName().Version.Revision;
            return verInfo;
        }

        public override bool Run()
        {
            throw new NotImplementedException();
        }

        public override List<MediaSearchReturn> SearchForMedia(MediaClass mediaObject)
        {
            logger.Log("Entering SearchForMedia(MediaClass)");
            List<MediaSearchReturn> returnValue = null;
            if (!File.Exists(mediaObject.Filename))
            {
                logger.Log(Logger.Levels.WARN, "mediaObject.Filename==" + mediaObject.Filename + " doesn't exist");
                logger.Log(Logger.Levels.ERROR, "Exiting SearchForMedia(" + mediaObject.Filename + "), rc==null");
                return null;
            }
            returnValue = SearchForMediaByName(mediaObject.Filename);
            logger.Log("Exiting SearchForMedia(" + mediaObject.Filename + "), with: " + returnValue.Count + " values");
            return returnValue;
        }

        public override List<MediaSearchReturn> SearchForMedia(MediaClass mediaObject, int maxReturns)
        {
            throw new NotImplementedException();
        }

        public override List<MediaSearchReturn> SearchForMediaByName(string name, int maxReturns)
        {
            throw new NotImplementedException();
        }

        public override List<MediaSearchReturn> SearchForMediaByName(string name)
        {
            logger.Log("Entering SearchForMediaByName(" + name + ")");
            var returnValue = new List<MediaSearchReturn>();
            if (titlesClass == null)
            {
                titlesClass = new Titles(config, useMovieNameDB, pluginEntity);
            }
#if DEBUG
            logger.Log("Skipping check for file existing in DEBUG");
#else
            if (!File.Exists(name))
            {
                logger.Log(Logger.Levels.WARN, "Filename==" + name + " doesn't exist");
                logger.Log(Logger.Levels.ERROR, "Exiting SearchForMediaByName(" + name + "), rc==null");
                return null;
            }
#endif

            // Remove the filesystem from the path
            foreach (FileSystemEntity fsE in config.FileSystems)
            {
                name = name.Replace(fsE.directory, "");
            }

            // Remove the extension
            name = name.Replace(Path.GetExtension(name), "");

            // trim any filesystem name (c:\, z:\, etc) from the path
            if (name.Contains(@":\"))
            {
                name = name.Substring(0, name.IndexOf(@":\", System.StringComparison.Ordinal) + 2);
            }

            // Get the directory name of the full path to the file, and remove any remaining bad characters
            name = trimInvalidPathCharacters(name);

            // replace whitespace
            foreach (char c in whitespaceRegex)
            {
                name = name.Replace(c.ToString(CultureInfo.InvariantCulture), " ");
            }

            // Get the filename+previous 2 directories (If they are availalble) and assign to workingPath
            string workingPath;
            string[] path = name.Split(Path.DirectorySeparatorChar);
            workingPath = Path.GetFileName(name);
            if (path.Length >= 3)
            {
                workingPath = Path.Combine(path[path.Length - 3],
                                           (Path.Combine(path[path.Length - 2], path[path.Length - 1])));
            }
            else
            {
                if (path.Length == 2)
                {
                    workingPath = Path.Combine(path[path.Length - 2],
                                               path[path.Length - 1]);
                }
            }
            logger.Log(String.Format("Using Working Path of: {0}", workingPath));

            // Now replace whitespace
            if (!String.IsNullOrWhiteSpace(whitespaceRegex))
            {
                foreach (char c in whitespaceRegex)
                {
                    workingPath = workingPath.Replace(c.ToString(CultureInfo.InvariantCulture), " ");
                }
                logger.Log(String.Format("Path post whitespace Regex: {0}", workingPath));
            }

            string[] pathComponents = workingPath.Split(Path.DirectorySeparatorChar);

            int episode = -1;
            int season = -1;
            var titles = new List<string>();
            var years = new List<string>();
            string episodeStr = String.Empty;
            string seasonStr = String.Empty;

            // string is the title, bool is if it is in a trie
            var generatedTitles = new Dictionary<string, bool>();
            var fuzzyGeneratedTitles = new List<string>();

            foreach (string component in pathComponents)
            {
                var codecs = new List<string>();
                var formats = new List<string>();
                string componentName = component;

                logger.Log(String.Format("Working on path component: {0}", component));
                foreach (Regex regex in regexs)
                {
                    // First as the filename only
                    MatchCollection mc = regex.Matches(component);
                    foreach (Match match in mc)
                    {
                        foreach (Group e in match.Groups["EPISODE"].Captures)
                        {
                            int.TryParse(e.Value, out episode);
                            episodeStr = e.Value;
                        }
                        foreach (Group s in match.Groups["SEASON"].Captures)
                        {
                            int.TryParse(s.Value, out season);
                            seasonStr = s.Value;
                        }
                        foreach (Group s in match.Groups["TITLE"].Captures)
                        {
                            titles.Add(s.Value);
                            if (s.Value.Contains(" and "))
                            {
                                string t = s.Value.Replace(" and ", " & ");
                                titles.Add(t);
                            }
                            if (s.Value.Contains(" & "))
                            {
                                string t = s.Value.Replace(" & ", " and ");
                                titles.Add(t);
                            }
                        }
                        foreach (Group s in match.Groups["YEAR"].Captures)
                        {
                            if (!years.Contains(s.Value))
                            {
                                years.Add(s.Value);
                            }
                        }
                        foreach (Group s in match.Groups["CODEC"].Captures)
                        {
                            codecs.Add(s.Value);
                        }
                        foreach (Group s in match.Groups["FORMAT"].Captures)
                        {
                            formats.Add(s.Value);
                        }
                    }
                }

                foreach (string s in codecs)
                {
                    logger.Log(String.Format("Removing {0} from filename {1}, as it is a defined codec", s, component));
                    componentName = component.Replace(s, component);
                }

                foreach (string s in formats)
                {
                    logger.Log(String.Format("Removing {0} from filename {1}, as it is a defined format", s, component));
                    componentName = component.Replace(s, component);
                }

                foreach (string s in years)
                {
                    logger.Log(String.Format("Removing {0} from filename {1}, as it is a defined year", s, component));
                    componentName = component.Replace(s, component);
                }

                if (episode >= 0)
                {
                    logger.Log(String.Format("Removing {0} from filename {1}, as it is a defined episode", episodeStr, component));
                    componentName = component.Replace(episodeStr, component);
                }

                if (season >= 0)
                {
                    logger.Log(String.Format("Removing {0} from filename {1}, as it is a defined season", seasonStr, component));
                    componentName = component.Replace(seasonStr, component);
                }

                componentName = Regex.Replace(componentName, removalStrings, "");

                string[] words = componentName.Split(' ');
                for (int start = words.Length - 1; start >= 0; start--)
                {
                    string title = String.Empty;
                    for (int wordCounter = 0; wordCounter < start; wordCounter++)
                    {
                        if (title.Equals(String.Empty))
                        {
                            title = words[wordCounter];
                        }
                        else
                        {
                            title = String.Format("{0} {1}", title, words[wordCounter]);
                        }
                    }
                    if (!generatedTitles.ContainsKey(title.Trim()))
                    {
                        if (!String.IsNullOrWhiteSpace(title))
                        {
                            generatedTitles.Add(title.Trim(), titlesClass.TitleExists(title.Trim()));
                            if (title.Contains(" and "))
                            {
                                string t = title.Replace(" and ", " & ");
                                generatedTitles.Add(t.Trim(), titlesClass.TitleExists(t.Trim()));
                            }
                            if (title.Contains(" & "))
                            {
                                string t = title.Replace(" & ", " and ");
                                generatedTitles.Add(t.Trim(), titlesClass.TitleExists(t.Trim()));
                            }
                        }
                    }
                    if (!String.IsNullOrWhiteSpace(title))
                    {
                        IList<string> fuzzyTitles = titlesClass.GetFuzzyMatches(title, fuzzyMatchDistance);

                        foreach (string s in fuzzyTitles)
                        {
                            if (!String.IsNullOrWhiteSpace(s))
                            {
                                if (fuzzyGeneratedTitles.Contains(s.Trim()))
                                {
                                    fuzzyGeneratedTitles.Add(s.Trim());
                                }
                            }
                        }
                    }
                }
                for (int start = 0; start < words.Length; start++)
                {
                    string title = String.Empty;
                    for (int wordCounter = 0; wordCounter < words.Length; wordCounter++)
                    {
                        if (title.Equals(String.Empty))
                        {
                            title = words[wordCounter];
                        }
                        else
                        {
                            title = String.Format("{0} {1}", title, words[wordCounter]);
                        }
                    }
                    if (!generatedTitles.ContainsKey(title.Trim()))
                    {
                        if (!String.IsNullOrWhiteSpace(title))
                        {
                            generatedTitles.Add(title.Trim(), titlesClass.TitleExists(title.Trim()));
                        }
                    }
                    if (!String.IsNullOrWhiteSpace(title))
                    {
                        IList<string> fuzzyTitles = titlesClass.GetFuzzyMatches(title, fuzzyMatchDistance);

                        foreach (string s in fuzzyTitles)
                        {
                            if (!String.IsNullOrWhiteSpace(s))
                            {
                                if (fuzzyGeneratedTitles.Contains(s.Trim()))
                                {
                                    fuzzyGeneratedTitles.Add(s.Trim());
                                    if (s.Contains("and"))
                                    {
                                        string t = s.Replace("and", "&");
                                        generatedTitles.Add(t.Trim(), titlesClass.TitleExists(t.Trim()));
                                    }
                                    if (s.Contains("&"))
                                    {
                                        string t = s.Replace("&", "and");
                                        generatedTitles.Add(t.Trim(), titlesClass.TitleExists(t.Trim()));
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (years.Count == 0)
            {
                years.Add("0000");
            }

            // At this point we now have our potential episodes, seasons, titles.  Time to see if they work
            // First starting with regex ones, then going to generated ones, finally if we dont have a good candidate
            // we go to the fuzzy generated ones

            var mediaSearchReturns = new List<MediaSearchReturn>();

            if (years.Count == 0)
            {
                years.Add(null);
            }

            foreach (string title in titles)
            {
                foreach (string year in years)
                {
                    if (!String.IsNullOrWhiteSpace(title))
                    {
                        mediaSearchReturns.AddRange(searchTMDB(title.Trim(), year, title.Trim()));
                        mediaSearchReturns.AddRange(searchTVDB(title.Trim(), year, title.Trim(), episode, season));
                    }
                }
            }

            // Lets see if we have something better than 90%, if so we will go with it
            foreach (MediaSearchReturn msr in mediaSearchReturns)
            {
                if (msr.pctLikelyMatch >= 90)
                {
                    storeUserDictionary(mediaSearchReturns);
                    logger.Log("Exiting SearchForMediaByName(" + name + "), with: " + mediaSearchReturns.Count + " values");
                    mediaSearchReturns.Sort();
                    mediaSearchReturns.Reverse();
                    titlesClass.Save();
                    return mediaSearchReturns;
                }
            }

            // now lets try our generated titles that have something in the trie
            foreach (KeyValuePair<string, bool> kvp in generatedTitles)
            {
                if (kvp.Value)
                {
                    foreach (string year in years)
                    {
                        mediaSearchReturns.AddRange(searchTMDB(kvp.Key, year, workingPath));
                        mediaSearchReturns.AddRange(searchTVDB(kvp.Key, year, workingPath, season, episode));
                    }
                }
            }

            // Lets see if we have something better than 90%, if so we will go with it
            foreach (MediaSearchReturn msr in mediaSearchReturns)
            {
                if (msr.pctLikelyMatch >= 90)
                {
                    storeUserDictionary(mediaSearchReturns);
                    logger.Log("Exiting SearchForMediaByName(" + name + "), with: " + mediaSearchReturns.Count + " values");
                    mediaSearchReturns.Sort();
                    mediaSearchReturns.Reverse();
                    titlesClass.Save();
                    return mediaSearchReturns;
                }
            }

            // Finally, things that were not in the trie
            foreach (KeyValuePair<string, bool> kvp in generatedTitles)
            {
                if (!kvp.Value)
                {
                    foreach (string year in years)
                    {
                        mediaSearchReturns.AddRange(searchTMDB(kvp.Key, year, workingPath));
                        mediaSearchReturns.AddRange(searchTVDB(kvp.Key, year, workingPath, season, episode));
                    }
                }
            }

            storeUserDictionary(mediaSearchReturns);
            titlesClass.Save();
            logger.Log("Exiting SearchForMediaByName(" + name + "), with: " + mediaSearchReturns.Count + " values");

            mediaSearchReturns = getUniqueMSR(mediaSearchReturns);

            mediaSearchReturns.Sort();
            mediaSearchReturns.Reverse();
            return mediaSearchReturns;
        }

        private List<MediaSearchReturn> getUniqueMSR(IList<MediaSearchReturn> msrInput)
        {
            var returnValue = new List<MediaSearchReturn>();
            foreach (MediaSearchReturn msr in msrInput)
            {
                bool inList = false;
                foreach (MediaSearchReturn msrCopied in returnValue)
                {
                    if (msrCopied.name.Equals(msr.name) && msrCopied.TMDBID == msr.TMDBID && msrCopied.TVDBID == msr.TVDBID)
                    {
                        inList = true;
                    }
                }
                if (!inList)
                {
                    returnValue.Add(msr);
                }
            }
            return returnValue;
        }

        private void storeUserDictionary(List<MediaSearchReturn> msrInput)
        {
            logger.Log("Entering FileNameProvider.storeUserDictionary()");
            foreach (MediaSearchReturn msr in msrInput)
            {
                titlesClass.AddTitle(msr.name);
            }
        }

        private MediaSearchReturn getTVDBEpisodeInfo(MediaSearchReturn msr, string path)
        {
            logger.Log(String.Format("Entering FileNameProvider.GetTVDBEpisodeInfo({0},{1})", msr.name, path));

            if (tvdbHandler == null)
            {
                string cacheDirectory = String.Empty;
                ICacheProvider cacheProvider = null;
                bool legalCache = true;
                try
                {
                    cacheDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
                    Directory.CreateDirectory(cacheDirectory);
                }
                catch (Exception e)
                {
                    legalCache = false;
                    logger.Log("Exception creating cache directory, msg=" + e.Message);
                }

                if (legalCache)
                {
                    cacheProvider = new XmlCacheProvider(cacheDirectory);
                }
                tvdbHandler = new TvdbHandler(cacheProvider, "A4ADC1F25D1A6E64");
                tvdbHandler.InitCache();
            }

            TvdbSeries series = tvdbHandler.GetFullSeries(msr.TVDBID, TvdbLanguage.DefaultLanguage, false);
            for (int i = 0; i < series.Episodes.Count; i++)
            {
                if (series.Episodes[i].SeasonNumber == msr.season)
                {
                    if (series.Episodes[i].EpisodeNumber == msr.episode)
                    {
                        msr.pctLikelyMatch += 10;

                        // Now remove whitespace from episode name
                        string episodeName = series.Episodes[i].EpisodeName;
                        if (!String.IsNullOrWhiteSpace(episodeName))
                        {
                            episodeName = Regex.Replace(episodeName, whitespaceRegex, " ");

                            // Do same with path
                            path = Regex.Replace(path, whitespaceRegex, " ");

                            // See if it is in it, case insesnitive
                            if (path.ToUpper().Contains(episodeName.ToUpper()))
                            {
                                msr.pctLikelyMatch += 10;
                            }
                        }
                        return msr;
                    }
                }
            }
            logger.Log(String.Format("Exiting FileNameProvider.GetTVDBEpisodeInfo({0},{1})", msr.name, path));
            return msr;
        }

        public override List<MediaSearchReturn> SearchForMediaByNameAndYear(string name, int year)
        {
            throw new NotImplementedException();
        }

        public override List<MediaSearchReturn> SearchForMediaByNameAndYear(string name, int year, int maxReturns)
        {
            throw new NotImplementedException();
        }

        public override void SetDefaultConfig()
        {
            logger.Log("Entering FileNameProvider.SetDefaultConfig()");
            VersionInfo vi = GetVersionInfo();
            base.pluginEntity.BuildNumber = vi.BuildNumber;
            base.pluginEntity.MajorVersion = vi.MajorVersion;
            base.pluginEntity.MinorVersion = vi.MinorVersion;
            base.pluginEntity.Name = vi.Name;

            base.pluginEntity.SaveOrUpdate();

            // Config variables are:
            // Name - Regex (There may be many of this type)
            // Type - String
            // Provider ID - This guys ID
            // Description - Regex's to determine the Title, Year, Season and Episode from a filename
            // Value - The regex, described below

            // Name - Whitespace
            // Type - String
            // Provider ID - This guys ID
            // Description - Characters to be treated as whitespace

            // Name - UseMovieNameDB
            // Type - BOOL
            // Provider ID - This guys ID
            // Description - Use the provider's database of movies for figuring out the name

            // Name - FuzzyMatchDistance
            // Type - INT
            // Provider ID - This guys ID
            // Description - The distance from exact to match fuzzy titles, bigger numbers give more results, but could give
            //               bad results

            // Name - RemovalStrings
            // Type - String
            // Provider ID - This guys ID
            // Description - Things we remove from the path, denoting things like Special Edition, Director's Cut, etc
            //               in the form of a regex

            // REGEX How they work
            // Each regex will be fed the entire path relative to the media directory, for example if the media directory is C:\Media
            // and the video is in c:\Media\TheBestShowEver\Season1\Episode1.wmv, it will be fed TheBestShowEver\Season1\Episode1.wmv
            //
            // Each regex can return one of the following values as a variable:
            //  TITLE - The title of the show
            //  YEAR - The year of the show
            //  SEASON - If its a show with seasons
            //  EPISODE - Again if its a show with episodes
            //  EPISODENAME - The name of the episode if applicable
            //  FORMAT - 720p, 1080i, etc
            //  CODEC - XVID

            // ExtensionsHandled:
            // "AVI","Common video container format.  AVI is the abbreviation for Audio Video Interleve format developed by Microsoft"}
            // "263","H.263 encoded video file"
            // {"264","H.264/MPEG-4 AVC video file"},
            // {"bdmv","Blu-ray information file"},
            // {"divx","DivX Media Format"},
            // {"dvr-ms","Microsoft Digital Video Recording"},
            // {"m2t","MPEG movie file"},
            // {"m2ts","MPEG-2 stream (Blu-Ray)"},
            // {"m4v","MPEG-4 video file format"},
            // {"mkv","Matroska video-audio multimedia file"},
            // {"mov","Apple QuickTime digital movie file"},
            // {"mp4","MPEG-4 video file format"},
            // {"mpeg","MPEG Movie"},
            // {"mpg","ProgDVB MPEG2 video file"},
            // {"wmv","Windows Media Video"},
            // {"xvid","XviD Codec"},
            // {"wtv","Windows Media Center recorded television file"}};

            string[] regexs =
                {
                    "(?<YEAR>19[0-9][0-9])|(?<YEAR>20[0-9][0-9])",
                    @"^(?<MONTH>0[1-9]|1[012])[- /.](?<DAY>0[1-9]|[12][0-9]|3[01])[- /.](?<YEAR>(19|20)\d\d)$",
                    @"(?<FORMAT>\b1080p\b|\b1080i\b|\b720p\b|\b576p\b|\b480p\b|\b480i\b|\b240p\b)",
                    @"(?<CODEC>\bAVI\b|\b263\b|\b264\b|\bbdmv\b|\bdivx\b|\bdvr\-ms\b|\bm2t\b|\bm2ts\b|\bm4v\b|\bmkv\b|\bmov\b|\bmp4\b|\bmpeg\b\bmpg\b|\bwmv\b|\bxvid\b|\bwtv\b)"
                    ,
                    @"(?<TITLE>.+)S(?<SEASON>[0-9]{1,2})E(?<EPISODE>[0-9]{1,2})",
                    @"(?<TITLE>.+)season[ |-|.](?<SEASON>[0-9]{1,2})\s*episode[ |-|.](?<EPISODE>[0-9]{1,2})",
                    @"(?<TITLE>.+)\\season[ |-|.](?<SEASON>[0-9]{1,2})\\.*episode[ |-|.](?<EPISODE>[0-9]{1,2})(?<EPISODETITLE>.*)",
                    @"(?<TITLE>.+)\d{4}"
                };
            string whitespace = @"[() .-_]";

            var ciH = new ConfigItemsHelper(config);
            this.regexs.Clear();

            ciH.CreateCIEntry(base.pluginEntity.ID, "RemovalStrings", EValueType.STRING, "(Special Edition|Director's Cut)", "Things we remove from the path, denoting things like Special Edition, Director's Cut, etc in the form of a regex");
            logger.Log("RemovalStrings: (Special Edition|Director's Cut)");
            this.removalStrings = "(Special Edition|Director's Cut)";

            ciH.CreateCIEntry(base.pluginEntity.ID, "FuzzyMatchDistance", EValueType.INT, "2", "Fuzzy title match distance");
            logger.Log("FuzzyMatchDistance set to 2");
            this.fuzzyMatchDistance = 2;

            foreach (string regex in regexs)
            {
                ciH.CreateCIEntry(base.pluginEntity.ID, "Regex", EValueType.STRING, regex,
                                  "Regular expression for figuring out the title from a filename");
                logger.Log(
                    string.Format(
                        "Set FilenameProvider Variable - Name: Regex, Value:{0}, Type: String, ProviderID:{1}", regex,
                        base.pluginEntity.ID));
                try
                {
                    this.regexs.Add(new Regex(regex, RegexOptions.Compiled | RegexOptions.IgnoreCase));
                }
                catch (Exception e)
                {
                    logger.Log(Logger.Levels.WARN,
                               String.Format("Exception creating regex: {0}, message={1}", regex, e.Message));
                }
            }
            ciH.CreateCIEntry(base.pluginEntity.ID, "Whitespace", EValueType.STRING, whitespace,
                              "Defines a set of characters to be treated as whitespace in the filename");
            logger.Log(string.Format(
                "Created FilenameProvider Variable - Name:{0}, Value:{1}, Type:{2}, ProviderID:{3}", "Whitespace",
                whitespace, "STRING", base.pluginEntity.ID.ToString()));
            this.whitespaceRegex = whitespace;

            ciH.CreateCIEntry(base.pluginEntity.ID, "UseMovieNameDB", EValueType.BOOL, "TRUE",
                              "Use the included database of movie names");
            logger.Log(string.Format(
                "Set FilenameProvider Variable - Name: UseMovieNameDB, TRUE, BOOL, ProviderID:{0}",
                base.pluginEntity.ID.ToString()));
            this.useMovieNameDB = true;

            var Extensions = new string[17, 2]
                                 {
                                     {
                                         "AVI",
                                         "Common video container format.  AVI is the abbreviation for Audio Video Interleve format developed by Microsoft"
                                     },
                                     {"263", "H.263 encoded video file"},
                                     {"264", "H.264/MPEG-4 AVC video file"},
                                     {"bdmv", "Blu-ray information file"},
                                     {"divx", "DivX Media Format"},
                                     {"dvr-ms", "Microsoft Digital Video Recording"},
                                     {"m2t", "MPEG movie file"},
                                     {"m2ts", "MPEG-2 stream (Blu-Ray)"},
                                     {"m4v", "MPEG-4 video file format"},
                                     {"mkv", "Matroska video-audio multimedia file"},
                                     {"mov", "Apple QuickTime digital movie file"},
                                     {"mp4", "MPEG-4 video file format"},
                                     {"mpeg", "MPEG Movie"},
                                     {"mpg", "ProgDVB MPEG2 video file"},
                                     {"wmv", "Windows Media Video"},
                                     {"xvid", "XviD Codec"},
                                     {"wtv", "Windows Media Center recorded television file"}
                                 };
            if (base.pluginEntity.ExtensionsHandled == null)
            {
                base.pluginEntity.ExtensionsHandled = new List<MediaFileTypesEntity>();
            }

            for (int i = 0; i < Extensions.Length / 2; i++)
            {
                var tmp = new MediaFileTypesEntity();
                tmp.Description = Extensions[i, 1];
                tmp.Extension = Extensions[i, 0];
                tmp.SaveOrUpdate();
                base.pluginEntity.ExtensionsHandled.Add(tmp);
            }

            base.pluginEntity.SaveOrUpdate();

            // Generate the IMDB Ternary Trie for figuring out what the titles are
            // For debug we wont recreate the actual file

#if !DEBUG
            string srcFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
                                          "IMDBTitles.dat");
            if (File.Exists(srcFile))
            {
                File.Copy(srcFile, Path.Combine(Path.GetDirectoryName(base.pluginEntity.Location), "IMDBTitles.dat"));
            }
#endif

            if (useMovieNameDB &&
                !File.Exists(Path.Combine(Path.GetDirectoryName(base.pluginEntity.Location), "IMDBTitles.dat")))
            {
                useMovieNameDB = CreateIMDBTernaryTrie();
            }
        }

        private bool CreateIMDBTernaryTrie()
        {
            logger.Log("Entering CreateIMDBTernaryTrie()");
            var lineRegex = new Regex("\"(?<TITLE>.+)\".*{(?<EPISODE>.+)}", RegexOptions.Compiled);
            var lineRegex2 = new Regex(@"(?<TITLE>.+) \(.{4}\)");

            var tst = new Tst.TstDictionary();

            FtpWebResponse WebResponse = null;

            bool success = false;

            try
            {
                FtpWebRequest getFile =
                    (FtpWebRequest)WebRequest.Create("ftp://ftp.fu-berlin.de/pub/misc/movies/database/movies.list.gz");

                WebResponse = (FtpWebResponse)getFile.GetResponse();
                success = true;
            }
            catch (Exception e)
            {
                logger.Log(Logger.Levels.WARN, String.Format("Exception attempting to download file: {0}", e.Message));
            }

            if (!success)
            {
                try
                {
                    FtpWebRequest getFile =
                        (FtpWebRequest)
                        WebRequest.Create("ftp://ftp.funet.fi/pub/mirrors/ftp.imdb.com/pub/movies.list.gz");

                    WebResponse = (FtpWebResponse)getFile.GetResponse();
                    success = true;
                }
                catch (Exception e)
                {
                    logger.Log(Logger.Levels.ERROR,
                               String.Format("Exception attempting to download file: {0}", e.Message));
                    logger.Log("Exiting FileNameProvider.CreateIMDBTernaryTrie(), rc==false");
                    return false;
                }
            }
            Stream responseStream = new GZipStream(WebResponse.GetResponseStream(), CompressionMode.Decompress);

            List<string> titles;
            StreamReader reader;
            using (reader = new StreamReader(responseStream, Encoding.Default))
            {
                titles = new List<string>();

                string line;

                while ((line = reader.ReadLine()) != null)
                {
                    line = Regex.Replace(line, whitespaceRegex, " ");
                    MatchCollection mc = lineRegex.Matches(line);
                    foreach (Match match in mc)
                    {
                        string Title = match.Groups["TITLE"].Captures[0].Value;
                        string Episode = Regex.Replace(match.Groups["EPISODE"].Captures[0].Value, "\\(.+\\)", "");
                        if (!String.IsNullOrWhiteSpace(Title))
                        {
                            titles.Add(Title);
                        }
                        else
                        {
                            MatchCollection mc2 = lineRegex2.Matches(line);
                            {
                                foreach (Match m2 in mc2)
                                {
                                    string Title2 = match.Groups["TITLE"].Captures[0].Value;
                                    if (!String.IsNullOrWhiteSpace(Title2))
                                    {
                                        titles.Add(Title2);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            titles.Sort();
            int a = 0;
            var dedupeTitles = new List<string>();
            while (a < titles.Count)
            {
                dedupeTitles.Add(titles[a]);
                a++;
                if (a < titles.Count)
                {
                    while (titles[a - 1] == titles[a])
                    {
                        a++;
                        if (a == titles.Count)
                        {
                            break;
                        }
                    }
                }
            }

            // Now Random Sort and add
            Random random = new Random();
            while (dedupeTitles.Count > 0)
            {
                int num = random.Next(dedupeTitles.Count);
                tst.Add(dedupeTitles[num], 1);
                dedupeTitles.RemoveAt(num);
            }
            string movieFileName = Path.Combine(Path.GetDirectoryName(base.pluginEntity.Location), "IMDBTitles.dat");
            try
            {
                logger.Log(String.Format("Saving {0}", movieFileName));
                tst.Save(movieFileName);
            }
            catch (Exception e)
            {
                logger.Log(Logger.Levels.ERROR,
                           String.Format("Failed to save the file {0}, exception: {1}", movieFileName, e.Message));
                logger.Log("Exiting CreateIMDBTernaryTrie(), rc=false");
                return false;
            }
            logger.Log("Exiting CreateIMDBTernaryTrie(), rc=true");
            return true;
        }

        private List<MediaSearchReturn> searchTMDB(string title, string year, string filename)
        {
            logger.Log(String.Format("Entering FileNameProvider.searchTMDB({0},{1},{2})", title, year, filename));
            if (TMDB_API == null)
            {
                TMDB_API = new Tmdb("6ed2aa34f177f0ead9943df1bbb3d88d");
            }

            var msr = new List<MediaSearchReturn>();
            int page = 1;
            int iYear;
            int.TryParse(year, out iYear);
            if (year.Equals("0000"))
            {
                year = String.Empty;
            }
            TmdbMovieSearch results = TMDB_API.SearchMovie(title + " " + year, page);
            while (results != null && results.results != null && results.results.Count != 0 && page <= 2)
            {
                foreach (MovieResult movie in results.results)
                {
                    var nMSR = new MediaSearchReturn();

                    nMSR.name = movie.title;
                    try
                    {
                        nMSR.year = DateTime.Parse(movie.release_date).Year;
                    }
                    catch (Exception)
                    {
                        nMSR.year = 0;
                    }
                    nMSR.TMDBID = movie.id;
                    TmdbMovie details = TMDB_API.GetMovieInfo(movie.id);
                    if (details != null)
                    {
                        nMSR.IMDBID = details.imdb_id;
                        nMSR.TMDBID = movie.id;
                        nMSR.TVDBID = 0;
                    }

                    // Get the best match for the section of the path that we are working on
                    float likelyMatch = 0;
                    string[] sections = filename.Split(Path.DirectorySeparatorChar);
                    foreach (string s in sections)
                    {
                        // TODO: Fix this for Bolt, its buggered.
                        float stringDistance = (int)Strings.ComputeStringDistance(nMSR.name, title);
                        float yearDistance = Math.Abs(20 - (Math.Abs(nMSR.year - iYear) * 2));
                        if (yearDistance > 20)
                        {
                            yearDistance = 20;
                        }
                        if (yearDistance < 0)
                        {
                            yearDistance = 0;
                        }
                        string pathSansYear = s.Replace(iYear.ToString(CultureInfo.InvariantCulture), "").Trim();

                        // Change this to a percentage the path distance is from the actual path size
                        // For example
                        // E and Bolt is only 4, but its radically different

                        float pathDistance = (float)(Strings.ComputeStringDistance(nMSR.name, pathSansYear) * 3);

                        float pathPctDistance = (float)(Strings.ComputeStringDistance(nMSR.name, pathSansYear) / pathSansYear.Length);

                        int containsName = 0;
                        if (pathSansYear.Trim().Equals(nMSR.name.Trim()))
                        {
                            containsName = 30;
                        }

                        likelyMatch = (80 - (stringDistance * 2) + yearDistance - pathDistance + containsName - pathPctDistance * 10);

                        if (likelyMatch > nMSR.pctLikelyMatch)
                        {
                            nMSR.pctLikelyMatch = likelyMatch;
                        }
                    }
                    if (nMSR.name.ToUpper().Contains(title.ToUpper()))
                    {
                        nMSR.pctLikelyMatch += 10;
                    }
                    else
                    {
                        if (Strings.ComputeStringDistance(nMSR.name.ToUpper(), title.ToUpper()) < 2)
                        {
                            nMSR.pctLikelyMatch -= 2;
                        }
                        else
                        {
                            nMSR.pctLikelyMatch -= 20;
                        }
                    }
                    if (nMSR.pctLikelyMatch < 0)
                    {
                        nMSR.pctLikelyMatch = 0;
                    }
                    if (nMSR.pctLikelyMatch > 100)
                    {
                        nMSR.pctLikelyMatch = 100;
                    }
                    msr.Add(nMSR);
                    msr.Sort();
                    msr.Reverse();
                }
                page++;
                results = TMDB_API.SearchMovie(title + " " + year, page);
            }
            logger.Log(String.Format("Entering FileNameProvider.searchTMDB({0},{1},{3}), with {2} returns", title, year,
                                     msr.Count, filename));

            return msr;
        }

        private List<MediaSearchReturn> searchTVDB(string title, string year, string filename, int episode, int season)
        {
            logger.Log(String.Format("Entering FileNameProvider.searchTVDB({0},{1},{2})", title, year, filename));
            if (tvdbHandler == null)
            {
                string cacheDirectory = String.Empty;
                ICacheProvider cacheProvider = null;
                bool legalCache = true;
                try
                {
                    cacheDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
                    Directory.CreateDirectory(cacheDirectory);
                }
                catch (Exception e)
                {
                    legalCache = false;
                    logger.Log("Exception creating cache directory, msg=" + e.Message);
                }

                if (legalCache)
                {
                    cacheProvider = new XmlCacheProvider(cacheDirectory);
                }
                tvdbHandler = new TvdbHandler(cacheProvider, "A4ADC1F25D1A6E64");
                tvdbHandler.InitCache();
            }

            var returnValue = new List<MediaSearchReturn>();

            List<TvdbSearchResult> results = tvdbHandler.SearchSeries(title);

            int iYear;
            int.TryParse(year, out iYear);

            foreach (TvdbSearchResult result in results)
            {
                MediaSearchReturn msr = new MediaSearchReturn();
                msr.IMDBID = result.ImdbId;
                msr.TVDBID = result.Id;
                msr.name = result.SeriesName;
                msr.season = season;
                msr.episode = episode;

                float pctLikelyMatch = 0;
                string[] sections = filename.Split(Path.DirectorySeparatorChar);
                foreach (string s in sections)
                {
                    float stringDistance = (int)Strings.ComputeStringDistance(msr.name, title);
                    float yearDistance = Math.Abs(20 - (Math.Abs(msr.year - iYear) * 2));
                    if (yearDistance > 20)
                    {
                        yearDistance = 20;
                    }
                    if (yearDistance < 0)
                    {
                        yearDistance = 0;
                    }

                    string pathSansYear = s.Replace(iYear.ToString(CultureInfo.InvariantCulture), "").Trim();
                    var pathDistance = (float)(Strings.ComputeStringDistance(msr.name, pathSansYear) * 3);

                    float pathPctDistance = (float)(Strings.ComputeStringDistance(msr.name, pathSansYear) / pathSansYear.Length);

                    int containsName = 0;
                    if (pathSansYear.Trim().Equals(msr.name.Trim()))
                    {
                        containsName = 30;
                    }

                    pctLikelyMatch = (80 - (stringDistance * 2) + yearDistance - pathDistance + containsName - (pathPctDistance - 1) * 10);

                    if (pctLikelyMatch > msr.pctLikelyMatch)
                    {
                        msr.pctLikelyMatch = pctLikelyMatch;
                    }
                    if (msr.pctLikelyMatch < 0)
                    {
                        msr.pctLikelyMatch = 0;
                    }
                    if (msr.pctLikelyMatch > 100)
                    {
                        msr.pctLikelyMatch = 100;
                    }
                    TvdbSeries series = tvdbHandler.GetFullSeries(msr.TVDBID, TvdbLanguage.DefaultLanguage, false);
                    bool yearMatch = false;
                    bool seasonMatch = false;
                    bool episodeMatch = false;
                    bool episodeNameMatch = false;
                    if (series != null)
                    {
                        foreach (TvdbEpisode epi in series.Episodes)
                        {
                            if (epi.FirstAired.Year == iYear)
                            {
                                yearMatch = true;
                                break;
                            }
                            if (epi.EpisodeNumber == episode)
                            {
                                episodeMatch = true;
                            }
                            if (epi.SeasonNumber == season)
                            {
                                seasonMatch = true;
                            }
                            if (filename.ToUpperInvariant().Contains(epi.EpisodeName.ToUpperInvariant()))
                            {
                                episodeNameMatch = true;
                            }
                        }

                        if (episodeNameMatch)
                        {
                            msr.pctLikelyMatch += 25;
                        }

                        if (yearMatch)
                        {
                            msr.pctLikelyMatch += 10;
                        }

                        if (seasonMatch)
                        {
                            msr.pctLikelyMatch += 10;
                        }
                        if (episodeMatch)
                        {
                            msr.pctLikelyMatch += 10;
                        }

                        returnValue.Add(msr);
                        logger.Log(String.Format("TVDB Series found: {0} {1}% likely match", msr.name,
                                                 msr.pctLikelyMatch));
                    }
                }
            }
            logger.Log(String.Format("Exiting FileNameProvider.searchTVDB({0},{1},{3}), with {2} matches", title,
                         year,
                         returnValue.Count, filename));
            return returnValue;
        }

        private string trimInvalidPathCharacters(string path)
        {
            string invalid = new string(Path.GetInvalidPathChars());
            foreach (char c in invalid)
            {
                path = path.Replace(c.ToString(CultureInfo.InvariantCulture), "");
            }
            return path;
        }

        private IEnumerable<string> sortByLength(IEnumerable<string> input)
        {
            var sorted = from s in input orderby s.Length descending select s;
            return sorted;
        }
    }
}